/*
 * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.javac.file;

import static com.sun.tools.javac.main.OptionName.D;
import static com.sun.tools.javac.main.OptionName.S;
import static javax.tools.StandardLocation.CLASS_OUTPUT;
import static javax.tools.StandardLocation.SOURCE_OUTPUT;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipFile;

import javax.lang.model.SourceVersion;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;

import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
import com.sun.tools.javac.file.RelativePath.RelativeFile;
import com.sun.tools.javac.main.OptionName;
import com.sun.tools.javac.util.BaseFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;

/**
 * This class provides access to the source, class and other files used by the
 * compiler and related tools.
 * 
 * <p>
 * <b>This is NOT part of any supported API. If you write code that depends on
 * this, you do so at your own risk. This code and its internal interfaces are
 * subject to change or deletion without notice.</b>
 */
public class JavacFileManager extends BaseFileManager implements
		StandardJavaFileManager {

	boolean useZipFileIndex;

	public static char[] toArray(CharBuffer buffer) {
		if (buffer.hasArray())
			return ((CharBuffer) buffer.compact().flip()).array();
		else
			return buffer.toString().toCharArray();
	}

	/**
	 * Encapsulates knowledge of paths
	 */
	private Paths paths;

	private FSInfo fsInfo;

	private final File uninited = new File("U N I N I T E D");

	private final Set<JavaFileObject.Kind> sourceOrClass = EnumSet.of(
			JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);

	/**
	 * The standard output directory, primarily used for classes. Initialized by
	 * the "-d" option. If classOutDir = null, files are written into same
	 * directory as the sources they were generated from.
	 */
	private File classOutDir = uninited;

	/**
	 * The output directory, used when generating sources while processing
	 * annotations. Initialized by the "-s" option.
	 */
	private File sourceOutDir = uninited;

	protected boolean mmappedIO;
	protected boolean ignoreSymbolFile;

	/**
	 * Register a Context.Factory to create a JavacFileManager.
	 */
	public static void preRegister(final Context context) {
		context.put(JavaFileManager.class,
				new Context.Factory<JavaFileManager>() {
					public JavaFileManager make() {
						return new JavacFileManager(context, true, null);
					}
				});
	}

	/**
	 * Create a JavacFileManager using a given context, optionally registering
	 * it as the JavaFileManager for that context.
	 */
	public JavacFileManager(Context context, boolean register, Charset charset) {
		super(charset);
		if (register)
			context.put(JavaFileManager.class, this);
		setContext(context);
	}

	/**
	 * Set the context for JavacFileManager.
	 */
	@Override
	public void setContext(Context context) {
		super.setContext(context);
		if (paths == null) {
			paths = Paths.instance(context);
		} else {
			// Reuse the Paths object as it stores the locations that
			// have been set with setLocation, etc.
			paths.setContext(context);
		}

		fsInfo = FSInfo.instance(context);

		useZipFileIndex = System.getProperty("useJavaUtilZip") == null;// TODO:
																		// options.get("useJavaUtilZip")
																		// ==
																		// null;

		mmappedIO = options.get("mmappedIO") != null;
		ignoreSymbolFile = options.get("ignore.symbol.file") != null;
	}

	public JavaFileObject getFileForInput(String name) {
		return getRegularFile(new File(name));
	}

	public JavaFileObject getRegularFile(File file) {
		return new RegularFileObject(this, file);
	}

	public JavaFileObject getFileForOutput(String classname,
			JavaFileObject.Kind kind, JavaFileObject sibling)
			throws IOException {
		return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
	}

	public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(
			Iterable<String> names) {
		ListBuffer<File> files = new ListBuffer<File>();
		for (String name : names)
			files.append(new File(nullCheck(name)));
		return getJavaFileObjectsFromFiles(files.toList());
	}

	public Iterable<? extends JavaFileObject> getJavaFileObjects(
			String... names) {
		return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
	}

	private static boolean isValidName(String name) {
		// Arguably, isValidName should reject keywords (such as in
		// SourceVersion.isName() ),
		// but the set of keywords depends on the source level, and we don't
		// want
		// impls of JavaFileManager to have to be dependent on the source level.
		// Therefore we simply check that the argument is a sequence of
		// identifiers
		// separated by ".".
		for (String s : name.split("\\.", -1)) {
			if (!SourceVersion.isIdentifier(s))
				return false;
		}
		return true;
	}

	private static void validateClassName(String className) {
		if (!isValidName(className))
			throw new IllegalArgumentException("Invalid class name: "
					+ className);
	}

	private static void validatePackageName(String packageName) {
		if (packageName.length() > 0 && !isValidName(packageName))
			throw new IllegalArgumentException("Invalid packageName name: "
					+ packageName);
	}

	public static void testName(String name, boolean isValidPackageName,
			boolean isValidClassName) {
		try {
			validatePackageName(name);
			if (!isValidPackageName)
				throw new AssertionError("Invalid package name accepted: "
						+ name);
			printAscii("Valid package name: \"%s\"", name);
		} catch (IllegalArgumentException e) {
			if (isValidPackageName)
				throw new AssertionError("Valid package name rejected: " + name);
			printAscii("Invalid package name: \"%s\"", name);
		}
		try {
			validateClassName(name);
			if (!isValidClassName)
				throw new AssertionError("Invalid class name accepted: " + name);
			printAscii("Valid class name: \"%s\"", name);
		} catch (IllegalArgumentException e) {
			if (isValidClassName)
				throw new AssertionError("Valid class name rejected: " + name);
			printAscii("Invalid class name: \"%s\"", name);
		}
	}

	private static void printAscii(String format, Object... args) {
		String message;
		try {
			final String ascii = "US-ASCII";
			message = new String(String.format(null, format, args).getBytes(
					ascii), ascii);
		} catch (java.io.UnsupportedEncodingException ex) {
			throw new AssertionError(ex);
		}
		System.out.println(message);
	}

	/**
	 * Insert all files in subdirectory `subdirectory' of `directory' which end
	 * in one of the extensions in `extensions' into packageSym.
	 */
	private void listDirectory(File directory, RelativeDirectory subdirectory,
			Set<JavaFileObject.Kind> fileKinds, boolean recurse,
			ListBuffer<JavaFileObject> l) {
		Archive archive = archives.get(directory);

		boolean isFile = fsInfo.isFile(directory);

		if (archive != null || isFile) {
			if (archive == null) {
				try {
					archive = openArchive(directory);
				} catch (IOException ex) {
					log.error("error.reading.file", directory, getMessage(ex));
					return;
				}
			}

			List<String> files = archive.getFiles(subdirectory);
			if (files != null) {
				for (String file; !files.isEmpty(); files = files.tail) {
					file = files.head;
					if (isValidFile(file, fileKinds)) {
						l.append(archive.getFileObject(subdirectory, file));
					}
				}
			}
			if (recurse) {
				for (RelativeDirectory s : archive.getSubdirectories()) {
					if (subdirectory.contains(s)) {
						// Because the archive map is a flat list of
						// directories,
						// the enclosing loop will pick up all child
						// subdirectories.
						// Therefore, there is no need to recurse deeper.
						listDirectory(directory, s, fileKinds, false, l);
					}
				}
			}
		} else {
			File d = subdirectory.getFile(directory);
			if (!caseMapCheck(d, subdirectory))
				return;

			File[] files = d.listFiles();
			if (files == null)
				return;

			for (File f : files) {
				String fname = f.getName();
				if (f.isDirectory()) {
					if (recurse && SourceVersion.isIdentifier(fname)) {
						listDirectory(directory, new RelativeDirectory(
								subdirectory, fname), fileKinds, recurse, l);
					}
				} else {
					if (isValidFile(fname, fileKinds)) {
						JavaFileObject fe = new RegularFileObject(this, fname,
								new File(d, fname));
						l.append(fe);
					}
				}
			}
		}
	}

	private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
		JavaFileObject.Kind kind = getKind(s);
		return fileKinds.contains(kind);
	}

	private static final boolean fileSystemIsCaseSensitive = File.separatorChar == '/';

	/**
	 * Hack to make Windows case sensitive. Test whether given path ends in a
	 * string of characters with the same case as given name. Ignore file
	 * separators in both path and name.
	 */
	private boolean caseMapCheck(File f, RelativePath name) {
		if (fileSystemIsCaseSensitive)
			return true;
		// Note that getCanonicalPath() returns the case-sensitive
		// spelled file name.
		String path;
		try {
			path = f.getCanonicalPath();
		} catch (IOException ex) {
			return false;
		}
		char[] pcs = path.toCharArray();
		char[] ncs = name.path.toCharArray();
		int i = pcs.length - 1;
		int j = ncs.length - 1;
		while (i >= 0 && j >= 0) {
			while (i >= 0 && pcs[i] == File.separatorChar)
				i--;
			while (j >= 0 && ncs[j] == '/')
				j--;
			if (i >= 0 && j >= 0) {
				if (pcs[i] != ncs[j])
					return false;
				i--;
				j--;
			}
		}
		return j < 0;
	}

	/**
	 * An archive provides a flat directory structure of a ZipFile by mapping
	 * directory names to lists of files (basenames).
	 */
	public interface Archive {
		void close() throws IOException;

		boolean contains(RelativePath name);

		JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);

		List<String> getFiles(RelativeDirectory subdirectory);

		Set<RelativeDirectory> getSubdirectories();
	}

	public class MissingArchive implements Archive {
		final File zipFileName;

		public MissingArchive(File name) {
			zipFileName = name;
		}

		public boolean contains(RelativePath name) {
			return false;
		}

		public void close() {
		}

		public JavaFileObject getFileObject(RelativeDirectory subdirectory,
				String file) {
			return null;
		}

		public List<String> getFiles(RelativeDirectory subdirectory) {
			return List.nil();
		}

		public Set<RelativeDirectory> getSubdirectories() {
			return Collections.emptySet();
		}

		@Override
		public String toString() {
			return "MissingArchive[" + zipFileName + "]";
		}
	}

	/**
	 * A directory of zip files already opened.
	 */
	Map<File, Archive> archives = new HashMap<File, Archive>();

	private static final String[] symbolFileLocation = { "lib", "ct.sym" };
	private static final RelativeDirectory symbolFilePrefix = new RelativeDirectory(
			"META-INF/sym/rt.jar/");

	/**
	 * Open a new zip file directory.
	 */
	protected Archive openArchive(File zipFileName) throws IOException {
		Archive archive = archives.get(zipFileName);
		if (archive == null) {
			File origZipFileName = zipFileName;
			if (!ignoreSymbolFile && paths.isBootClassPathRtJar(zipFileName)) {
				File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
				if (new File(file.getName()).equals(new File("jre")))
					file = file.getParentFile();
				// file == ${jdk.home}
				for (String name : symbolFileLocation)
					file = new File(file, name);
				// file == ${jdk.home}/lib/ct.sym
				if (file.exists())
					zipFileName = file;
			}

			try {

				ZipFile zdir = null;

				boolean usePreindexedCache = false;
				String preindexCacheLocation = null;

				if (!useZipFileIndex) {
					zdir = new ZipFile(zipFileName);
				} else {
					usePreindexedCache = options.get("usezipindex") != null;
					preindexCacheLocation = options.get("java.io.tmpdir");
					String optCacheLoc = options.get("cachezipindexdir");

					if (optCacheLoc != null && optCacheLoc.length() != 0) {
						if (optCacheLoc.startsWith("\"")) {
							if (optCacheLoc.endsWith("\"")) {
								optCacheLoc = optCacheLoc.substring(1,
										optCacheLoc.length() - 1);
							} else {
								optCacheLoc = optCacheLoc.substring(1);
							}
						}

						File cacheDir = new File(optCacheLoc);
						if (cacheDir.exists() && cacheDir.canWrite()) {
							preindexCacheLocation = optCacheLoc;
							if (!preindexCacheLocation.endsWith("/")
									&& !preindexCacheLocation
											.endsWith(File.separator)) {
								preindexCacheLocation += File.separator;
							}
						}
					}
				}

				if (origZipFileName == zipFileName) {
					if (!useZipFileIndex) {
						archive = new ZipArchive(this, zdir);
					} else {
						archive = new ZipFileIndexArchive(
								this,
								ZipFileIndex.getZipFileIndex(
										zipFileName,
										null,
										usePreindexedCache,
										preindexCacheLocation,
										options.get("writezipindexfiles") != null));
					}
				} else {
					if (!useZipFileIndex) {
						archive = new SymbolArchive(this, origZipFileName,
								zdir, symbolFilePrefix);
					} else {
						archive = new ZipFileIndexArchive(
								this,
								ZipFileIndex.getZipFileIndex(
										zipFileName,
										symbolFilePrefix,
										usePreindexedCache,
										preindexCacheLocation,
										options.get("writezipindexfiles") != null));
					}
				}
			} catch (FileNotFoundException ex) {
				archive = new MissingArchive(zipFileName);
			} catch (IOException ex) {
				if (zipFileName.exists())
					log.error("error.reading.file", zipFileName, getMessage(ex));
				archive = new MissingArchive(zipFileName);
			}

			archives.put(origZipFileName, archive);
		}
		return archive;
	}

	/**
	 * Flush any output resources.
	 */
	public void flush() {
		contentCache.clear();
	}

	/**
	 * Close the JavaFileManager, releasing resources.
	 */
	public void close() {
		for (Iterator<Archive> i = archives.values().iterator(); i.hasNext();) {
			Archive a = i.next();
			i.remove();
			try {
				a.close();
			} catch (IOException e) {
			}
		}
	}

	private String defaultEncodingName;

	@SuppressWarnings("unused")
	private String getDefaultEncodingName() {
		if (defaultEncodingName == null) {
			defaultEncodingName = new OutputStreamWriter(
					new ByteArrayOutputStream()).getEncoding();
		}
		return defaultEncodingName;
	}

	public ClassLoader getClassLoader(Location location) {
		nullCheck(location);
		Iterable<? extends File> path = getLocation(location);
		if (path == null)
			return null;
		ListBuffer<URL> lb = new ListBuffer<URL>();
		for (File f : path) {
			try {
				lb.append(f.toURI().toURL());
			} catch (MalformedURLException e) {
				throw new AssertionError(e);
			}
		}

		return getClassLoader(lb.toArray(new URL[lb.size()]));
	}

	public Iterable<JavaFileObject> list(Location location, String packageName,
			Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
		// validatePackageName(packageName);
		nullCheck(packageName);
		nullCheck(kinds);

		Iterable<? extends File> path = getLocation(location);
		if (path == null)
			return List.nil();
		RelativeDirectory subdirectory = RelativeDirectory
				.forPackage(packageName);
		ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();

		for (File directory : path)
			listDirectory(directory, subdirectory, kinds, recurse, results);

		return results.toList();
	}

	public String inferBinaryName(Location location, JavaFileObject file) {
		file.getClass(); // null check
		location.getClass(); // null check
		// Need to match the path semantics of list(location, ...)
		Iterable<? extends File> path = getLocation(location);
		if (path == null) {
			return null;
		}

		if (file instanceof BaseFileObject) {
			return ((BaseFileObject) file).inferBinaryName(path);
		} else
			throw new IllegalArgumentException(file.getClass().getName());
	}

	public boolean isSameFile(FileObject a, FileObject b) {
		nullCheck(a);
		nullCheck(b);
		if (!(a instanceof BaseFileObject))
			throw new IllegalArgumentException("Not supported: " + a);
		if (!(b instanceof BaseFileObject))
			throw new IllegalArgumentException("Not supported: " + b);
		return a.equals(b);
	}

	public boolean hasLocation(Location location) {
		return getLocation(location) != null;
	}

	public JavaFileObject getJavaFileForInput(Location location,
			String className, JavaFileObject.Kind kind) throws IOException {
		nullCheck(location);
		// validateClassName(className);
		nullCheck(className);
		nullCheck(kind);
		if (!sourceOrClass.contains(kind))
			throw new IllegalArgumentException("Invalid kind " + kind);
		return getFileForInput(location, RelativeFile.forClass(className, kind));
	}

	public FileObject getFileForInput(Location location, String packageName,
			String relativeName) throws IOException {
		nullCheck(location);
		// validatePackageName(packageName);
		nullCheck(packageName);
		if (!isRelativeUri(relativeName))
			throw new IllegalArgumentException("Invalid relative name: "
					+ relativeName);
		RelativeFile name = packageName.length() == 0 ? new RelativeFile(
				relativeName) : new RelativeFile(
				RelativeDirectory.forPackage(packageName), relativeName);
		return getFileForInput(location, name);
	}

	private JavaFileObject getFileForInput(Location location, RelativeFile name)
			throws IOException {
		Iterable<? extends File> path = getLocation(location);
		if (path == null)
			return null;

		for (File dir : path) {
			if (dir.isDirectory()) {
				File f = name.getFile(dir);
				if (f.exists())
					return new RegularFileObject(this, f);
			} else {
				Archive a = openArchive(dir);
				if (a.contains(name)) {
					return a.getFileObject(name.dirname(), name.basename());
				}

			}
		}

		return null;
	}

	public JavaFileObject getJavaFileForOutput(Location location,
			String className, JavaFileObject.Kind kind, FileObject sibling)
			throws IOException {
		nullCheck(location);
		// validateClassName(className);
		nullCheck(className);
		nullCheck(kind);
		if (!sourceOrClass.contains(kind))
			throw new IllegalArgumentException("Invalid kind " + kind);
		return getFileForOutput(location,
				RelativeFile.forClass(className, kind), sibling);
	}

	public FileObject getFileForOutput(Location location, String packageName,
			String relativeName, FileObject sibling) throws IOException {
		nullCheck(location);
		// validatePackageName(packageName);
		nullCheck(packageName);
		if (!isRelativeUri(relativeName))
			throw new IllegalArgumentException("relativeName is invalid");
		RelativeFile name = packageName.length() == 0 ? new RelativeFile(
				relativeName) : new RelativeFile(
				RelativeDirectory.forPackage(packageName), relativeName);
		return getFileForOutput(location, name, sibling);
	}

	private JavaFileObject getFileForOutput(Location location,
			RelativeFile fileName, FileObject sibling) throws IOException {
		File dir;
		if (location == CLASS_OUTPUT) {
			if (getClassOutDir() != null) {
				dir = getClassOutDir();
			} else {
				File siblingDir = null;
				if (sibling != null && sibling instanceof RegularFileObject) {
					siblingDir = ((RegularFileObject) sibling).file
							.getParentFile();
				}
				return new RegularFileObject(this, new File(siblingDir,
						fileName.basename()));
			}
		} else if (location == SOURCE_OUTPUT) {
			dir = (getSourceOutDir() != null ? getSourceOutDir()
					: getClassOutDir());
		} else {
			Iterable<? extends File> path = paths.getPathForLocation(location);
			dir = null;
			for (File f : path) {
				dir = f;
				break;
			}
		}

		File file = fileName.getFile(dir); // null-safe
		return new RegularFileObject(this, file);

	}

	public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
			Iterable<? extends File> files) {
		ArrayList<RegularFileObject> result;
		if (files instanceof Collection<?>)
			result = new ArrayList<RegularFileObject>(
					((Collection<?>) files).size());
		else
			result = new ArrayList<RegularFileObject>();
		for (File f : files)
			result.add(new RegularFileObject(this, nullCheck(f)));
		return result;
	}

	public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
		return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
	}

	public void setLocation(Location location, Iterable<? extends File> path)
			throws IOException {
		nullCheck(location);
		paths.lazy();

		final File dir = location.isOutputLocation() ? getOutputDirectory(path)
				: null;

		if (location == CLASS_OUTPUT)
			classOutDir = getOutputLocation(dir, D);
		else if (location == SOURCE_OUTPUT)
			sourceOutDir = getOutputLocation(dir, S);
		else
			paths.setPathForLocation(location, path);
	}

	// where
	private File getOutputDirectory(Iterable<? extends File> path)
			throws IOException {
		if (path == null)
			return null;
		Iterator<? extends File> pathIter = path.iterator();
		if (!pathIter.hasNext())
			throw new IllegalArgumentException("empty path for directory");
		File dir = pathIter.next();
		if (pathIter.hasNext())
			throw new IllegalArgumentException("path too long for directory");
		if (!dir.exists())
			throw new FileNotFoundException(dir + ": does not exist");
		else if (!dir.isDirectory())
			throw new IOException(dir + ": not a directory");
		return dir;
	}

	private File getOutputLocation(File dir, OptionName defaultOptionName) {
		if (dir != null)
			return dir;
		String arg = options.get(defaultOptionName);
		if (arg == null)
			return null;
		return new File(arg);
	}

	public Iterable<? extends File> getLocation(Location location) {
		nullCheck(location);
		paths.lazy();
		if (location == CLASS_OUTPUT) {
			return (getClassOutDir() == null ? null : List.of(getClassOutDir()));
		} else if (location == SOURCE_OUTPUT) {
			return (getSourceOutDir() == null ? null : List
					.of(getSourceOutDir()));
		} else
			return paths.getPathForLocation(location);
	}

	private File getClassOutDir() {
		if (classOutDir == uninited)
			classOutDir = getOutputLocation(null, D);
		return classOutDir;
	}

	private File getSourceOutDir() {
		if (sourceOutDir == uninited)
			sourceOutDir = getOutputLocation(null, S);
		return sourceOutDir;
	}

	/**
	 * Enforces the specification of a "relative" URI as used in
	 * {@linkplain #getFileForInput(Location,String,URI) getFileForInput}. This
	 * method must follow the rules defined in that method, do not make any
	 * changes without consulting the specification.
	 */
	protected static boolean isRelativeUri(URI uri) {
		if (uri.isAbsolute())
			return false;
		String path = uri.normalize().getPath();
		if (path.length() == 0 /* isEmpty() is mustang API */)
			return false;
		char first = path.charAt(0);
		return first != '.' && first != '/';
	}

	// Convenience method
	protected static boolean isRelativeUri(String u) {
		try {
			return isRelativeUri(new URI(u));
		} catch (URISyntaxException e) {
			return false;
		}
	}

	/**
	 * Converts a relative file name to a relative URI. This is different from
	 * File.toURI as this method does not canonicalize the file before creating
	 * the URI. Furthermore, no schema is used.
	 * 
	 * @param file
	 *            a relative file name
	 * @return a relative URI
	 * @throws IllegalArgumentException
	 *             if the file name is not relative according to the definition
	 *             given in {@link javax.tools.JavaFileManager#getFileForInput}
	 */
	public static String getRelativeName(File file) {
		if (!file.isAbsolute()) {
			String result = file.getPath().replace(File.separatorChar, '/');
			if (isRelativeUri(result))
				return result;
		}
		throw new IllegalArgumentException("Invalid relative path: " + file);
	}

	/**
	 * Get a detail message from an IOException. Most, but not all, instances of
	 * IOException provide a non-null result for getLocalizedMessage(). But some
	 * instances return null: in these cases, fallover to getMessage(), and if
	 * even that is null, return the name of the exception itself.
	 * 
	 * @param e
	 *            an IOException
	 * @return a string to include in a compiler diagnostic
	 */
	public static String getMessage(IOException e) {
		String s = e.getLocalizedMessage();
		if (s != null)
			return s;
		s = e.getMessage();
		if (s != null)
			return s;
		return e.toString();
	}
}
